{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "!pip install pfhedge"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import matplotlib\n",
    "import matplotlib.pyplot as plt\n",
    "import numpy as np\n",
    "import seaborn\n",
    "import torch"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "seaborn.set_style(\"whitegrid\")\n",
    "\n",
    "FONTSIZE = 18\n",
    "matplotlib.rcParams[\"figure.figsize\"] = (10, 5)\n",
    "matplotlib.rcParams[\"figure.dpi\"] = 300\n",
    "matplotlib.rcParams[\"figure.titlesize\"] = FONTSIZE\n",
    "matplotlib.rcParams[\"font.family\"] = \"sans-serif\"\n",
    "matplotlib.rcParams[\"legend.fontsize\"] = FONTSIZE\n",
    "matplotlib.rcParams[\"xtick.labelsize\"] = FONTSIZE\n",
    "matplotlib.rcParams[\"ytick.labelsize\"] = FONTSIZE\n",
    "matplotlib.rcParams[\"axes.labelsize\"] = FONTSIZE\n",
    "matplotlib.rcParams[\"axes.titlesize\"] = FONTSIZE\n",
    "matplotlib.rcParams[\"savefig.bbox\"] = \"tight\"\n",
    "matplotlib.rcParams[\"savefig.pad_inches\"] = 0.1\n",
    "matplotlib.rcParams[\"lines.linewidth\"] = 2\n",
    "matplotlib.rcParams[\"axes.linewidth\"] = 1.6"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "torch.manual_seed(42)\n",
    "\n",
    "if not torch.cuda.is_available():\n",
    "    raise RuntimeWarning(\n",
    "        \"CUDA is not available. \"\n",
    "        \"If you're using Google Colab, you can enable GPUs as: \"\n",
    "        \"https://colab.research.google.com/notebooks/gpu.ipynb\"\n",
    "    )\n",
    "\n",
    "DEVICE = torch.device(\"cuda:0\" if torch.cuda.is_available() else \"cpu\")\n",
    "print(\"Default device:\", DEVICE)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# In each epoch, N_PATHS brownian motion time-series are generated.\n",
    "N_PATHS = 50000\n",
    "# How many times a model is updated in the experiment.\n",
    "N_EPOCHS = 200"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def to_numpy(tensor: torch.Tensor) -> np.array:\n",
    "    return tensor.cpu().detach().numpy()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## How to Use"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "tags": []
   },
   "source": [
    "### Prepare Instruments"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We consider a `BrownianStock`, which is a stock following the geometric Brownian motion, and a `EuropeanOption` which is contingent on it.\n",
    "\n",
    "We assume that the stock has a transaction cost given by `cost`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from pfhedge.instruments import BrownianStock, EuropeanOption\n",
    "\n",
    "stock = BrownianStock(cost=1e-3)\n",
    "derivative = EuropeanOption(stock).to(DEVICE)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "derivative"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Create Your Hedger"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We here use a multi-layer perceptron as our model."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from pfhedge.nn import Hedger\n",
    "from pfhedge.nn import MultiLayerPerceptron\n",
    "\n",
    "model = MultiLayerPerceptron()\n",
    "hedger = Hedger(\n",
    "    model, inputs=[\"log_moneyness\", \"expiry_time\", \"volatility\", \"prev_hedge\"]\n",
    ").to(DEVICE)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The `hedger` is also a [`Module`](https://pytorch.org/docs/stable/generated/torch.nn.Module.html#torch.nn.Module)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "hedger"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "history = hedger.fit(derivative, n_epochs=N_EPOCHS, n_paths=N_PATHS, n_times=20)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plt.plot(history)\n",
    "plt.xlabel(\"Number of epochs\")\n",
    "plt.ylabel(\"Loss (entropic risk measure)\")\n",
    "plt.title(\"Loss histories for a European option\")\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pnl = hedger.compute_pnl(derivative, n_paths=50000)\n",
    "\n",
    "plt.figure()\n",
    "plt.hist(to_numpy(pnl), bins=100)\n",
    "plt.title(\"Profit-loss histograms of 50000 price paths for a European option\")\n",
    "plt.xlabel(\"Profit-loss\")\n",
    "plt.ylabel(\"Number of events\")\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "price = hedger.price(derivative)\n",
    "price"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## More Examples"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Black-Scholes' Delta-Hedging Strategy"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from pfhedge.nn import Hedger\n",
    "from pfhedge.nn import BlackScholes\n",
    "\n",
    "model = BlackScholes(derivative)\n",
    "hedger = Hedger(model, inputs=model.inputs()).to(DEVICE)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "hedger"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "price = hedger.price(derivative)\n",
    "price"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Whalley-Wilmott's Asymptotically Optimal Strategy for Small Costs"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from pfhedge.nn import Hedger\n",
    "from pfhedge.nn import WhalleyWilmott\n",
    "\n",
    "model = WhalleyWilmott(derivative)\n",
    "hedger = Hedger(model, inputs=model.inputs()).to(DEVICE)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "price = hedger.price(derivative)\n",
    "price"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Your Own Module"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import torch.nn.functional as fn\n",
    "from torch import Tensor\n",
    "from torch.nn import Module\n",
    "\n",
    "from pfhedge.nn import BlackScholes, Clamp, MultiLayerPerceptron\n",
    "\n",
    "\n",
    "class NoTransactionBandNet(Module):\n",
    "    def __init__(self, derivative):\n",
    "        super().__init__()\n",
    "\n",
    "        self.delta = BlackScholes(derivative)\n",
    "        self.mlp = MultiLayerPerceptron(out_features=2)\n",
    "        self.clamp = Clamp()\n",
    "\n",
    "    def inputs(self):\n",
    "        return self.delta.inputs() + [\"prev_hedge\"]\n",
    "\n",
    "    def forward(self, input: Tensor) -> Tensor:\n",
    "        prev_hedge = input[..., [-1]]\n",
    "\n",
    "        delta = self.delta(input[..., :-1])\n",
    "        width = self.mlp(input[..., :-1])\n",
    "\n",
    "        min = delta - fn.leaky_relu(width[..., [0]])\n",
    "        max = delta + fn.leaky_relu(width[..., [1]])\n",
    "\n",
    "        return self.clamp(prev_hedge, min=min, max=max)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "model = NoTransactionBandNet(derivative)\n",
    "hedger = Hedger(model, inputs=model.inputs()).to(DEVICE)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "history = hedger.fit(derivative, n_epochs=N_EPOCHS, n_paths=N_PATHS, n_times=20)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plt.plot(history)\n",
    "plt.xlabel(\"Number of epochs\")\n",
    "plt.ylabel(\"Loss (entropic risk measure)\")\n",
    "plt.title(\"Loss histories for a European option\")\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pnl = hedger.compute_pnl(derivative, n_paths=50000)\n",
    "\n",
    "plt.figure()\n",
    "plt.hist(to_numpy(pnl), bins=100)\n",
    "plt.title(\"Profit-loss histograms of 50000 price paths for a European option\")\n",
    "plt.xlabel(\"Profit-loss\")\n",
    "plt.ylabel(\"Number of events\")\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "price = hedger.price(derivative)\n",
    "price"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Use Expected Shortfall as a Loss function"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from pfhedge.nn import ExpectedShortfall"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Expected shortfall with the quantile level of 10%\n",
    "expected_shortfall = ExpectedShortfall(0.1)\n",
    "\n",
    "model = NoTransactionBandNet(derivative)\n",
    "hedger = Hedger(model, inputs=model.inputs(), criterion=expected_shortfall).to(DEVICE)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "history = hedger.fit(derivative, n_epochs=N_EPOCHS, n_paths=N_PATHS, n_times=20)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plt.plot(history)\n",
    "plt.xlabel(\"Number of epochs\")\n",
    "plt.ylabel(\"Loss (entropic risk measure)\")\n",
    "plt.title(\"Loss histories for a European option\")\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pnl = hedger.compute_pnl(derivative, n_paths=50000)\n",
    "\n",
    "plt.figure()\n",
    "plt.hist(to_numpy(pnl), bins=100)\n",
    "plt.title(\"Profit-loss histograms of 50000 price paths for a European option\")\n",
    "plt.xlabel(\"Profit-loss\")\n",
    "plt.ylabel(\"Number of events\")\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "price = hedger.price(derivative)\n",
    "price"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "816ef0de",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "interpreter": {
   "hash": "d79b388a38d14f9c81809feed319a626759412745495b86064f5b8e2e524d076"
  },
  "kernelspec": {
   "display_name": "Python 3.9.5 64-bit ('pfhedge-YRrs2QHC-py3.9': poetry)",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.10"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
